package com.camunda.workflow.camunda.provider;

import com.camunda.workflow.domain.MapStructMapper;
import com.camunda.workflow.domain.entity.*;
import com.camunda.workflow.mapper.*;
import com.camunda.workflow.utils.EncryUtil;
import com.mybatisflex.core.query.QueryWrapper;
import org.camunda.bpm.engine.authorization.Permissions;
import org.camunda.bpm.engine.authorization.Resources;
import org.camunda.bpm.engine.identity.Group;
import org.camunda.bpm.engine.identity.Tenant;
import org.camunda.bpm.engine.identity.User;
import org.camunda.bpm.engine.impl.context.Context;
import org.camunda.bpm.engine.impl.identity.IdentityOperationResult;
import org.camunda.bpm.engine.impl.identity.WritableIdentityProvider;
import org.camunda.bpm.engine.impl.persistence.entity.*;
import org.camunda.bpm.engine.impl.util.EnsureUtil;

import java.util.List;
import java.util.concurrent.Callable;

public class CustomWritableIdentityProvider extends CustomReadOnlyIdentityProvider implements WritableIdentityProvider {
    public CustomWritableIdentityProvider(SysUserMapper sysUserMapper, SysRoleMapper sysRoleMapper, SysTenantMapper sysTenantMapper, SysUserRoleMapper sysUserRoleMapper, SysTenantUserRoleMapper sysTenantUserRoleMapper) {
        super(sysUserMapper, sysRoleMapper, sysTenantMapper, sysUserRoleMapper, sysTenantUserRoleMapper);
    }

    @Override
    public UserEntity createNewUser(String userId) {
        this.checkAuthorization(Permissions.CREATE, Resources.USER, null);
        return new UserEntity(userId);
    }

    @Override
    public IdentityOperationResult saveUser(User user) {
        UserEntity userEntity = (UserEntity) user;
        userEntity.setPassword(EncryUtil.passwdEnc(userEntity.getPassword()));
        SysUser sysUser = MapStructMapper.INSTANCE.toSysUser(userEntity);
        String operation;
        if (userEntity.getRevision() == 0) {
            operation = "create";
            this.checkAuthorization(Permissions.CREATE, Resources.USER, null);
            sysUserMapper.insert(sysUser);
            this.createDefaultAuthorizations(userEntity);
        } else {
            operation = "update";
            this.checkAuthorization(Permissions.UPDATE, Resources.USER, userEntity.getId());
            sysUserMapper.update(sysUser);
        }
        return new IdentityOperationResult(userEntity, operation);
    }

    @Override
    public IdentityOperationResult deleteUser(String userId) {
        this.checkAuthorization(Permissions.DELETE, Resources.USER, userId);
        UserEntity user = this.findUserById(userId);
        if (user != null) {
//            this.deleteMembershipsByUserId(userId);
//            this.deleteTenantMembershipsOfUser(userId);
            sysUserRoleMapper.deleteByQuery(QueryWrapper.create().where(SysUserRole::getUserId).eq(Long.parseLong(userId)));
            sysTenantUserRoleMapper.deleteByQuery(QueryWrapper.create().where(SysTenantUserRole::getUserId).eq(Long.parseLong(userId)));
            this.deleteAuthorizations(Resources.USER, userId);
            Context.getCommandContext().runWithoutAuthorization((Callable<Void>) () -> {
//                List<Tenant> tenants = createTenantQuery().userMember(userId).list();
                List<SysTenant> sysTenants = sysTenantMapper.selectTenantsByUserId(userId);
                List<TenantEntity> tenants = MapStructMapper.INSTANCE.toCamundaTenantList(sysTenants);
                if (tenants != null && !tenants.isEmpty()) {
                    for (Tenant tenant : tenants) {
                        deleteAuthorizationsForUser(Resources.TENANT, tenant.getId(), userId);
                    }
                }
                return null;
            });
//            this.getDbEntityManager().delete(user);
            sysUserMapper.deleteById(userId);
            return new IdentityOperationResult(null, "delete");
        } else {
            return new IdentityOperationResult(null, "none");
        }
    }

    @Override
    public IdentityOperationResult unlockUser(String userId) {
        UserEntity user = this.findUserById(userId);
        return user != null ? this.unlockUser(user) : new IdentityOperationResult(null, "none");
    }

    @Override
    public GroupEntity createNewGroup(String groupId) {
        this.checkAuthorization(Permissions.CREATE, Resources.GROUP, null);
        return new GroupEntity(groupId);
    }

    @Override
    public IdentityOperationResult saveGroup(Group group) {
        GroupEntity groupEntity = (GroupEntity) group;
        String operation;
        SysRole sysRole = MapStructMapper.INSTANCE.toSysRole(groupEntity);
        if (groupEntity.getRevision() == 0) {
            operation = "create";
            this.checkAuthorization(Permissions.CREATE, Resources.GROUP, null);
            sysRoleMapper.insert(sysRole);
            this.createDefaultAuthorizations(group);
        } else {
            operation = "update";
            this.checkAuthorization(Permissions.UPDATE, Resources.GROUP, group.getId());
            sysRoleMapper.update(sysRole);
        }
        return new IdentityOperationResult(groupEntity, operation);
    }

    @Override
    public IdentityOperationResult deleteGroup(String groupId) {
        this.checkAuthorization(Permissions.DELETE, Resources.GROUP, groupId);
        GroupEntity group = this.findGroupById(groupId);
        if (group != null) {
//            this.deleteMembershipsByGroupId(groupId);
//            this.deleteTenantMembershipsOfGroup(groupId);
            sysUserRoleMapper.deleteByQuery(QueryWrapper.create().where(SysUserRole::getRoleId).eq(Long.parseLong(groupId)));
            sysTenantUserRoleMapper.deleteByQuery(QueryWrapper.create().where(SysTenantUserRole::getRoleId).eq(Long.parseLong(groupId)));
            this.deleteAuthorizations(Resources.GROUP, groupId);
            Context.getCommandContext().runWithoutAuthorization((Callable<Void>) () -> {
//                List<Tenant> tenants = createTenantQuery().groupMember(groupId).list();
                List<SysTenant> sysTenants = sysTenantMapper.selectTenantsByRoleId(groupId);
                List<TenantEntity> tenants = MapStructMapper.INSTANCE.toCamundaTenantList(sysTenants);
                if (tenants != null && !tenants.isEmpty()) {
                    for (Tenant tenant : tenants) {
                        deleteAuthorizationsForGroup(Resources.TENANT, tenant.getId(), groupId);
                    }
                }
                return null;
            });
//            this.getDbEntityManager().delete(group);
            sysRoleMapper.deleteById(groupId);
            return new IdentityOperationResult(null, "delete");
        } else {
            return new IdentityOperationResult(null, "none");
        }
    }

    @Override
    public TenantEntity createNewTenant(String tenantId) {
        this.checkAuthorization(Permissions.CREATE, Resources.TENANT, null);
        return new TenantEntity(tenantId);
    }

    @Override
    public IdentityOperationResult saveTenant(Tenant tenant) {
        TenantEntity tenantEntity = (TenantEntity) tenant;
        String operation;
        SysTenant sysTenant = MapStructMapper.INSTANCE.toSysTenant(tenantEntity);
        if (tenantEntity.getRevision() == 0) {
            operation = "create";
            this.checkAuthorization(Permissions.CREATE, Resources.TENANT, null);
            sysTenantMapper.insert(sysTenant);
            this.createDefaultAuthorizations(tenant);
        } else {
            operation = "update";
            this.checkAuthorization(Permissions.UPDATE, Resources.TENANT, tenant.getId());
            sysTenantMapper.update(sysTenant);
        }
        return new IdentityOperationResult(tenantEntity, operation);
    }

    @Override
    public IdentityOperationResult deleteTenant(String tenantId) {
        this.checkAuthorization(Permissions.DELETE, Resources.TENANT, tenantId);
        TenantEntity tenant = this.findTenantById(tenantId);
        if (tenant != null) {
//            this.deleteTenantMembershipsOfTenant(tenantId);
            sysTenantUserRoleMapper.deleteByQuery(QueryWrapper.create().where(SysTenantUserRole::getTenantId).eq(Long.parseLong(tenantId)));
            this.deleteAuthorizations(Resources.TENANT, tenantId);
//            this.getDbEntityManager().delete(tenant);
            sysTenantMapper.deleteById(tenantId);
            return new IdentityOperationResult(null, "delete");
        } else {
            return new IdentityOperationResult(null, "none");
        }
    }

    @Override
    public IdentityOperationResult createMembership(String userId, String groupId) {
        this.checkAuthorization(Permissions.CREATE, Resources.GROUP_MEMBERSHIP, groupId);
        UserEntity user = this.findUserById(userId);
        EnsureUtil.ensureNotNull("No user found with id '" + userId + "'.", "user", user);
        GroupEntity group = this.findGroupById(groupId);
        EnsureUtil.ensureNotNull("No group found with id '" + groupId + "'.", "group", group);
        MembershipEntity membership = new MembershipEntity();
        membership.setUser(user);
        membership.setGroup(group);
//        this.getDbEntityManager().insert(membership);
        SysUserRole sysUserRole = MapStructMapper.INSTANCE.toSysUserRole(membership);
        sysUserRoleMapper.insert(sysUserRole);
        this.createDefaultMembershipAuthorizations(userId, groupId);
        return new IdentityOperationResult(null, "create");
    }

    @Override
    public IdentityOperationResult deleteMembership(String userId, String groupId) {
        this.checkAuthorization(Permissions.DELETE, Resources.GROUP_MEMBERSHIP, groupId);
        if (this.existsMembership(userId, groupId)) {
            this.deleteAuthorizations(Resources.GROUP_MEMBERSHIP, groupId);
//            Map<String, Object> parameters = new HashMap<>();
//            parameters.put("userId", userId);
//            parameters.put("groupId", groupId);
//            this.getDbEntityManager().delete(MembershipEntity.class, "deleteMembership", parameters);
            sysUserRoleMapper.deleteByQuery(QueryWrapper.create().where(SysUserRole::getUserId).eq(userId).and(SysUserRole::getRoleId).eq(groupId));
            return new IdentityOperationResult(null, "delete");
        } else {
            return new IdentityOperationResult(null, "none");
        }
    }

    @Override
    public IdentityOperationResult createTenantUserMembership(String tenantId, String userId) {
        this.checkAuthorization(Permissions.CREATE, Resources.TENANT_MEMBERSHIP, tenantId);
        TenantEntity tenant = this.findTenantById(tenantId);
        UserEntity user = this.findUserById(userId);
        EnsureUtil.ensureNotNull("No tenant found with id '" + tenantId + "'.", "tenant", tenant);
        EnsureUtil.ensureNotNull("No user found with id '" + userId + "'.", "user", user);
        TenantMembershipEntity membership = new TenantMembershipEntity();
        membership.setTenant(tenant);
        membership.setUser(user);
//        this.getDbEntityManager().insert(membership);
        SysTenantUserRole sysTenantUserRole = MapStructMapper.INSTANCE.toSysTenantUserRole(membership);
        sysTenantUserRoleMapper.insert(sysTenantUserRole);
        this.createDefaultTenantMembershipAuthorizations(tenant, user);
        return new IdentityOperationResult(null, "create");

    }

    @Override
    public IdentityOperationResult createTenantGroupMembership(String tenantId, String groupId) {
        this.checkAuthorization(Permissions.CREATE, Resources.TENANT_MEMBERSHIP, tenantId);
        TenantEntity tenant = this.findTenantById(tenantId);
        GroupEntity group = this.findGroupById(groupId);
        EnsureUtil.ensureNotNull("No tenant found with id '" + tenantId + "'.", "tenant", tenant);
        EnsureUtil.ensureNotNull("No group found with id '" + groupId + "'.", "group", group);
        TenantMembershipEntity membership = new TenantMembershipEntity();
        membership.setTenant(tenant);
        membership.setGroup(group);
//        this.getDbEntityManager().insert(membership);
        SysTenantUserRole sysTenantUserRole = MapStructMapper.INSTANCE.toSysTenantUserRole(membership);
        sysTenantUserRoleMapper.insert(sysTenantUserRole);
        this.createDefaultTenantMembershipAuthorizations(tenant, group);
        return new IdentityOperationResult(null, "create");
    }

    @Override
    public IdentityOperationResult deleteTenantUserMembership(String tenantId, String userId) {
        this.checkAuthorization(Permissions.DELETE, Resources.TENANT_MEMBERSHIP, tenantId);
        if (this.existsTenantMembership(tenantId, userId, null)) {
            this.deleteAuthorizations(Resources.TENANT_MEMBERSHIP, userId);
            this.deleteAuthorizationsForUser(Resources.TENANT, tenantId, userId);
//            Map<String, Object> parameters = new HashMap<>();
//            parameters.put("tenantId", tenantId);
//            parameters.put("userId", userId);
//            this.getDbEntityManager().delete(TenantMembershipEntity.class, "deleteTenantMembership", parameters);
            sysTenantUserRoleMapper.deleteByQuery(QueryWrapper.create().where(SysTenantUserRole::getTenant).eq(tenantId).and(SysTenantUserRole::getUser).eq(userId));
            return new IdentityOperationResult(null, "delete");
        } else {
            return new IdentityOperationResult(null, "none");
        }
    }

    @Override
    public IdentityOperationResult deleteTenantGroupMembership(String tenantId, String groupId) {
        this.checkAuthorization(Permissions.DELETE, Resources.TENANT_MEMBERSHIP, tenantId);
        if (this.existsTenantMembership(tenantId, null, groupId)) {
            this.deleteAuthorizations(Resources.TENANT_MEMBERSHIP, groupId);
            this.deleteAuthorizationsForGroup(Resources.TENANT, tenantId, groupId);
//            Map<String, Object> parameters = new HashMap<>();
//            parameters.put("tenantId", tenantId);
//            parameters.put("groupId", groupId);
//            this.getDbEntityManager().delete(TenantMembershipEntity.class, "deleteTenantMembership", parameters);
            sysTenantUserRoleMapper.deleteByQuery(QueryWrapper.create().where(SysTenantUserRole::getTenant).eq(tenantId).and(SysTenantUserRole::getRoleId).eq(groupId));
            return new IdentityOperationResult(null, "delete");
        } else {
            return new IdentityOperationResult(null, "none");
        }
    }


    protected IdentityOperationResult unlockUser(UserEntity user) {
        if (user.getAttempts() > 0 || user.getLockExpirationTime() != null) {
            getIdentityInfoManager().updateUserLock(user, 0, null);
            return new IdentityOperationResult(user, IdentityOperationResult.OPERATION_UNLOCK);
        }
        return new IdentityOperationResult(user, IdentityOperationResult.OPERATION_NONE);
    }

    // ======================== authorizations ========================
    protected void createDefaultAuthorizations(UserEntity userEntity) {
        if (Context.getProcessEngineConfiguration().isAuthorizationEnabled()) {
            saveDefaultAuthorizations(getResourceAuthorizationProvider().newUser(userEntity));
        }
    }

    protected void createDefaultAuthorizations(Group group) {
        if (isAuthorizationEnabled()) {
            saveDefaultAuthorizations(getResourceAuthorizationProvider().newGroup(group));
        }
    }

    protected void createDefaultAuthorizations(Tenant tenant) {
        if (isAuthorizationEnabled()) {
            saveDefaultAuthorizations(getResourceAuthorizationProvider().newTenant(tenant));
        }
    }

    protected void createDefaultMembershipAuthorizations(String userId, String groupId) {
        if (isAuthorizationEnabled()) {
            saveDefaultAuthorizations(getResourceAuthorizationProvider().groupMembershipCreated(groupId, userId));
        }
    }

    protected void createDefaultTenantMembershipAuthorizations(Tenant tenant, User user) {
        if (isAuthorizationEnabled()) {
            saveDefaultAuthorizations(getResourceAuthorizationProvider().tenantMembershipCreated(tenant, user));
        }
    }

    protected void createDefaultTenantMembershipAuthorizations(Tenant tenant, Group group) {
        if (isAuthorizationEnabled()) {
            saveDefaultAuthorizations(getResourceAuthorizationProvider().tenantMembershipCreated(tenant, group));
        }
    }
}
